Processing chain

// Start first chain: brightness -> clarity -> contrast -> pre warm/tint -> clarity -> vignette = result1
// Start new chain for lookup
// brightness -> clarity -> contrast -> pre warm/tint -> clarity -> vignette -> lookup = result2 (copy of result 1)
// Mix result with chain 1 and apply rest of the processing
// result1 + result2 -> post warmth/tint -> fade -> grain -> saturation -> dust -> Final Output
*******************************************************************
Context, filters for Metal vs normal vs RAW

In non-RAW application:

Initialised: differntly depending on Metal/non-Metal:

    CIContext *context;
    NSArray *keys = [NSArray arrayWithObjects:kCIContextWorkingColorSpace, nil];
    NSArray *objects = [NSArray arrayWithObjects:(__bridge id)(CGColorSpaceCreateDeviceRGB()) , nil];
    NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];

    // Create slightly different context in case of Metal
    if (isMetaldevice)
    {
    context = [CIContext contextWithMTLDevice:isMetaldevice options:dictionary];
    }
    else
    {
    context = [CIContext contextWithOptions: dictionary];
    }

Context is used:

- to get CGImageRef: CGImageRef cgImage = [context createCGImage:adjustedImage fromRect:[adjustedImage extent]];
- Also, in Metal mode only context is used to do rendering context render:result toMTLTexture

in RAW application

Initialised: differntly depending on RAW/non-RAW:

RAW:
    NSArray *keysR = [NSArray arrayWithObjects: kCIContextWorkingFormat, kCIContextWorkingColorSpace ,nil];
    NSArray *objectsR = [NSArray arrayWithObjects: [NSNumber numberWithInt:kCIFormatRGBAh], [NSNull null] , nil];
    NSDictionary *dictionaryR = [NSDictionary dictionaryWithObjects:objectsR forKeys:keysR];
    contextRaw = [CIContext contextWithOptions: dictionaryR];

Non RAW:

    NSArray *keys = [NSArray arrayWithObjects:kCIContextWorkingColorSpace, nil];
    NSArray *objects = [NSArray arrayWithObjects:(__bridge id)(CGColorSpaceCreateDeviceRGB()) , nil];
    NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
    context = [CIContext contextWithOptions: dictionary];

Context is used:

- to get CGImageRef:

    if (isRAWMode == NO)
    {
    cgImage = [context createCGImage:adjustedImage fromRect:originalSize];
    }
    else
    {
    cgImage = [contextRaw createCGImage:adjustedImage fromRect:originalSize];
    }

- and to get to get CGImageRef in getUIImageForRaw: CGImageRef cgImage = [contextRaw createCGImage:adjustedImage fromRect:originalSize];

Conclustion: contexts are used similarly but Options for RAW mode are different. Will RAW options work for Metal too? - YES, RAW options seems to work fine with metal.
    Eventually the same context object is used but with different options if RAW is specified

-----------------------------------------------------

Filter chain in non-RAW application

Filter chain in RAW application

*******************************************************************

Custom slider for dual zone is implemented using the following logic:

-100  -75	-50	-25	  0     25	 50     75	100
|_____|_____|____|____|_____|_____|_____|_____|
(-)6	(-) 3         0         (-)0.25     (-)0.5


Filter value to UI:
Q: How to determine which part of the range to use while conversing filter to UI
A: We store another value along with currentValue in Json that indicates if left or right part of a slider to use

if "useLeftRange" = true

if minValue < 0

-100		x			minScaleValue * filterV
——— =    ——- —->  uiV = ———————————————
-6		-3				minValue

if minValue > 0

-100		x			minScaleValue * filterV
——— =    ——- —->  uiV = ———————————————
6		3				minValue

if "useLeftRange" = false

if maxValue < 0

100		x			maxScaleValue * filterV
——— =    ——- —->  uiV = ———————————————
-0.5		-0.25				maxValue

if maxValue > 0

100		x			maxScaleValue * filterV
——— =    ——- —->  uiV = ———————————————
0.5		0.25				maxValue


UI to filter value:
if UI value < 0:

if minValue < 0

-100		-25				minValue * uiV
——— =    ——-	———> filterV = ————————
-6		x                   minScaleValue

if minValue > 0
-100	        -25				minValue * uiV
——— =    ——-   ———>  filterV = ————————
6		x                       minScaleValue

if UI value > 0:

if maxValue < 0

100		25                  maxValue * uiV
——— =    ——-	———> filterV = —————————
-0.5		x				  maxScaleValue

if maxValue > 0

100	         25				maxValue * uiV
——— =    ——-	———> filterV = ————————
0.5		x                   maxScaleValue


JSON format that supports filter unlock:

rniFilmCategories
    rniFilmPacks
        rniFilms
        packPreviews

CAtegory -> Pack -> films
                -> previews

{
"rniFilmCategories":[
    {
        "filterCategory":0,
        "rniFilmPacks":[
        {
            //	 free pack
            "packId": "001",  // Filter category plus unique number 0+001, 0+002 etc.
            "price": 0.00,
            "availabilityType": 1, // 1 - free, 2 - free unlock, 3 - paid, 4- bonus
            "isAvailable":true,
            "unlockMessage":"",
            "name":"Free filter pack",
            "packDescription":"Free filter pack that comes with app",
            "colour":"",
            "packLockedImage":"",
            "packPreviews":[
                {
                    "slideName":"preview1"
                },
                {
                    "slideName":"preview2"
                },
                {
                    "slideName":"preview3"
                }
            ],
            "rniFilms":[
                {
                "isNone":true,
                "isFirstInPack":true,
                "filterName":"None",
                "filterDescription":"None",
                "lookupImageName":"",
                "lookupId":"000000"
                },
                {
                "isNone":false,
                "isFirstInPack":false,
                "filterName":"Agfa\nOptima 100\nE",
                "filterDescription":"Agfa\nOptima 100\nE",
                "lookupImageName":"Agfa_Optima-100_E.png",
                "lookupId":"001001"
                },
                {
                "isNone":false,
                "isFirstInPack":false,
                "filterName":"Agfa\nOptima 100\nG",
                "filterDescription":"Agfa\nOptima 100\nG",
                "lookupImageName":"Agfa_Optima-100_G.png",
                "lookupId":"001002"
                },
                {
                "isNone":false,
                "isFirstInPack":false,
                "filterName":"Agfa\nOptima 100\nH",
                "filterDescription":"Agfa\nOptima 100\nH",
                "lookupImageName":"Agfa_Optima-100_H.png",
                "lookupId":"001003"
                }
            ]
            },
            {
                paid pack
            },
            {
                unlock pack
            }
            ]
            },
            {
                "filterCategory":1,
                "rniFilmPacks":[
                {
                free pack
                },
                {
                paid pack
                },
                {
                unlock pack
                }
        ] -- Film packs
    }
    ] --rniFilmCategories
}

// Updated:

"rniFilmCategories":[
{
"filterCategory":0, - номер как и раньше
"rniFilmPacks":[
{
"packId":"0001", - номер пакета фильтров в формате 0 (категория) + 001 (для первого пакета)
"price":0.00,
"availabilityType":1, - 1,2,3 - это категории пакетов фильтров: бесплатный, бесплатный анлок, платный
"isAvailable":true, - показывает доступен (куплен или анлокнут) данный пакет фильтров
"unlockMessage":"", - не используется пока. Это то слово Free, которое стало частью картинки с замочком
"name":"Free filter pack", - не используется. это сейчас тот текст, который в первой превьюхе зашит
"packDescription":"Free filter pack that comes with app", - отображается под превью картинками.
"colour":"", - цвет, объединяющий фильтры в пакеты в ленте
"packLockedImage":"", - замочек/корзина
"packLockedImage6Plus":"",  - замочек/корзина
"packPreviews4_5":[ - превью изображения для разных размеров айфонов
{
"slideName":""
},
{
"slideName":""
},
{
"slideName":""
}
],
"packPreviews6":[
{
"slideName":""
},
{
"slideName":""
},
{
"slideName":""
}
],
"packPreviews6Plus":[
{
"slideName":""
},
{
"slideName":""
},
{
"slideName":""
}
],
"rniFilms":[
{
"isNone":true,
"isFirstInPack":true, - это новое свойство. В пакете фильтров оно указывает на первую кнопку на которой нужно нарисовать закочек или корзину
"filterName":"None",
"filterDescription":"None",
"lookupImageName":"",
"lookupId":"0000000" - для кнопки None это всегда 7 нулей
},
{
"isNone":false,
"isFirstInPack":false,
"filterName":"Agfa\nOptima 100\nE",
"filterDescription":"Agfa\nOptima 100\nE",
"lookupImageName":"Agfa_Optima-100_E.png",
"lookupId":"0001001" - это 0 (категория) + 001 (packId)+ 001 (lookupId)
},
{
"isNone":false,
"isFirstInPack":false,
"filterName":"Agfa\nOptima 100\nG",
"filterDescription":"Agfa\nOptima 100\nG",
"lookupImageName":"Agfa_Optima-100_G.png",
"lookupId":"0001002"

iCLoud, main assumptions:
- Main data for filters comes from static JSON file embedded into app. This json only changes from one release to another. JSON is transformed into in-memory data structure
- This in-memory data structure is updated on a fly every time app launches  based on two sources:
    - NSUserDefaults
    - iCloud data

- NSUser defaults, in turn, is updated based on iCloud data

Sequence of actions when app starts: didFinishLaunchingWithOptions created iCloud manager and gets data from iCloud, this action also updates NSUserDefaults with iCloud data -> ViewController deserializes JSON into in-memory collection and updates it based on NSUserDefaults


- Master data of unlocked filter packs is NSUserDefaults
- iCloud data is only updated via ubiquitousKeyValueStoreDidChange method. This method is the only code that can update NSUserDefaults. Inside NSUserDefaults manager, validation logic will do checks to ignore duplicates


*********************
iPad adaptation:

On 'load photo' screen:
+ fix cog button blue dot
- add Flashback button
- add Colibri button
- review commented out code
- uidisabler
- hours buttons

Settings screen
